// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include <array>
#include <string_view>
#include <tuple>

#include "pw_preprocessor/compiler.h"
#include "pw_protobuf/internal/codegen.h"
#include "pw_span/span.h"
#include "pw_status/status.h"
#include "pw_status/status_with_size.h"
#include "pw_stream/memory_stream.h"
#include "pw_unit_test/framework.h"

// These header files contain the code generated by the pw_protobuf plugin.
// They are re-generated every time the tests are built and are used by the
// tests to ensure that the interface remains consistent.
//
// The purpose of the tests in this file is primarily to verify that the
// generated C++ interface is valid rather than the correctness of the
// low-level encoder.
#include "pw_protobuf_test_protos/full_test.pwpb.h"
#include "pw_protobuf_test_protos/importer.pwpb.h"
#include "pw_protobuf_test_protos/optional.pwpb.h"
#include "pw_protobuf_test_protos/repeated.pwpb.h"

namespace pw::protobuf {
namespace {

using namespace ::pw::protobuf::test::pwpb;

PW_MODIFY_DIAGNOSTICS_PUSH();
PW_MODIFY_DIAGNOSTIC(ignored, "-Wmissing-field-initializers");

TEST(CodegenMessage, Equality) {
  const Pigweed::Message one{
      .magic_number = 0x49u,
      .ziggy = -111,
      .cycles = 0x40302010fecaaddeu,
      .ratio = -1.42f,
      .error_message = "not a typewriter",
      .pigweed = {.status = Bool::FILE_NOT_FOUND},
      .bin = Pigweed::Protobuf::Binary::ZERO,
      .proto = {.bin = Proto::Binary::OFF,
                .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
                .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
                .meta =
                    {
                        .file_name = "/etc/passwd",
                        .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
                        .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
                        .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
                    }},
      .data = {std::byte{0x10},
               std::byte{0x20},
               std::byte{0x30},
               std::byte{0x40},
               std::byte{0x50},
               std::byte{0x60},
               std::byte{0x70},
               std::byte{0x80}},
      .bungle = -111,
  };

  const Pigweed::Message two{
      .magic_number = 0x49u,
      .ziggy = -111,
      .cycles = 0x40302010fecaaddeu,
      .ratio = -1.42f,
      .error_message = "not a typewriter",
      .pigweed = {.status = Bool::FILE_NOT_FOUND},
      .bin = Pigweed::Protobuf::Binary::ZERO,
      .proto = {.bin = Proto::Binary::OFF,
                .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
                .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
                .meta =
                    {
                        .file_name = "/etc/passwd",
                        .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
                        .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
                        .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
                    }},
      .data = {std::byte{0x10},
               std::byte{0x20},
               std::byte{0x30},
               std::byte{0x40},
               std::byte{0x50},
               std::byte{0x60},
               std::byte{0x70},
               std::byte{0x80}},
      .bungle = -111,
  };

  EXPECT_TRUE(one == two);
}

TEST(CodegenMessage, CopyEquality) {
  Pigweed::Message one{
      .magic_number = 0x49u,
      .ziggy = -111,
      .cycles = 0x40302010fecaaddeu,
      .ratio = -1.42f,
      .error_message = "not a typewriter",
      .pigweed = {.status = Bool::FILE_NOT_FOUND},
      .bin = Pigweed::Protobuf::Binary::ZERO,
      .proto = {.bin = Proto::Binary::OFF,
                .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
                .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
                .meta =
                    {
                        .file_name = "/etc/passwd",
                        .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
                        .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
                        .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
                    }},
      .data = {std::byte{0x10},
               std::byte{0x20},
               std::byte{0x30},
               std::byte{0x40},
               std::byte{0x50},
               std::byte{0x60},
               std::byte{0x70},
               std::byte{0x80}},
      .bungle = -111,
  };
  Pigweed::Message two = one;

  EXPECT_TRUE(one == two);
}

TEST(CodegenMessage, EmptyEquality) {
  const Pigweed::Message one{};
  const Pigweed::Message two{};

  EXPECT_TRUE(one == two);
}

TEST(CodegenMessage, Inequality) {
  const Pigweed::Message one{
      .magic_number = 0x49u,
      .ziggy = -111,
      .cycles = 0x40302010fecaaddeu,
      .ratio = -1.42f,
      .error_message = "not a typewriter",
      .pigweed = {.status = Bool::FILE_NOT_FOUND},
      .bin = Pigweed::Protobuf::Binary::ZERO,
      .proto = {.bin = Proto::Binary::OFF,
                .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
                .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
                .meta =
                    {
                        .file_name = "/etc/passwd",
                        .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
                        .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
                        .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
                    }},
      .data = {std::byte{0x10},
               std::byte{0x20},
               std::byte{0x30},
               std::byte{0x40},
               std::byte{0x50},
               std::byte{0x60},
               std::byte{0x70},
               std::byte{0x80}},
      .bungle = -111,
  };

  const Pigweed::Message two{
      .magic_number = 0x43u,
      .ziggy = 128,
      .ratio = -1.42f,
      .error_message = "not a typewriter",
      .pigweed = {.status = Bool::TRUE},
      .bin = Pigweed::Protobuf::Binary::ZERO,
      .proto = {.bin = Proto::Binary::OFF,
                .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
                .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ONE,
                .meta =
                    {
                        .file_name = "/etc/passwd",
                        .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
                        .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
                        .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
                    }},
      .data = {std::byte{0x20},
               std::byte{0x30},
               std::byte{0x40},
               std::byte{0x50},
               std::byte{0x60},
               std::byte{0x70},
               std::byte{0x80},
               std::byte{0x90}},
  };

  EXPECT_FALSE(one == two);
}

TEST(CodegenMessage, TriviallyComparable) {
  static_assert(IsTriviallyComparable<IntegerMetadata::Message>());
  static_assert(IsTriviallyComparable<KeyValuePair::Message>());
  static_assert(!IsTriviallyComparable<Pigweed::Message>());
}

TEST(CodegenMessage, ConstCopyable) {
  const Pigweed::Message one{
      .magic_number = 0x49u,
      .ziggy = -111,
      .cycles = 0x40302010fecaaddeu,
      .ratio = -1.42f,
      .error_message = "not a typewriter",
      .pigweed = {.status = Bool::FILE_NOT_FOUND},
      .bin = Pigweed::Protobuf::Binary::ZERO,
      .proto = {.bin = Proto::Binary::OFF,
                .pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO,
                .pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO,
                .meta =
                    {
                        .file_name = "/etc/passwd",
                        .status = Pigweed::Protobuf::Compiler::Status::FUBAR,
                        .protobuf_bin = Pigweed::Protobuf::Binary::ONE,
                        .pigweed_bin = Pigweed::Pigweed::Binary::ONE,
                    }},
      .data = {std::byte{0x10},
               std::byte{0x20},
               std::byte{0x30},
               std::byte{0x40},
               std::byte{0x50},
               std::byte{0x60},
               std::byte{0x70},
               std::byte{0x80}},
      .bungle = -111,
  };
  Pigweed::Message two = one;

  EXPECT_TRUE(one == two);
}

TEST(CodegenMessage, FixReservedIdentifiers) {
  // This test checks that the code was generated as expected, so it will simply
  // fail to compile if its expectations are not met.

  // Make sure that the `signed` field was renamed to `signed_`.
  std::ignore = IntegerMetadata::Message{
      .bits = 32,
      .signed_ = true,
      .null = false,
  };

  // Make sure that the internal enum describing the struct's fields was
  // generated as expected:
  // - `BITS` doesn't need an underscore.
  // - `SIGNED_` has an underscore to match the corresponding `signed_` field.
  // - `NULL_` has an underscore to avoid a collision with `NULL` (even though
  //   the field `null` doesn't have or need an underscore).
  std::ignore = IntegerMetadata::Fields::kBits;
  std::ignore = IntegerMetadata::Fields::kSigned;
  std::ignore = IntegerMetadata::Fields::kNull;

  // Make sure that the `ReservedWord` enum values were renamed as expected.
  // Specifically, only enum-value names that are reserved in UPPER_SNAKE_CASE
  // should be modified. Names that are only reserved in lower_snake_case should
  // be left alone since they'll never appear in that form in the generated
  // code.
  std::ignore = ReservedWord::NULL_;    // Add underscore since NULL is a macro.
  std::ignore = ReservedWord::kNull;    // No underscore necessary.
  std::ignore = ReservedWord::INT;      // No underscore necessary.
  std::ignore = ReservedWord::kInt;     // No underscore necessary.
  std::ignore = ReservedWord::RETURN;   // No underscore necessary.
  std::ignore = ReservedWord::kReturn;  // No underscore necessary.
  std::ignore = ReservedWord::BREAK;    // No underscore necessary.
  std::ignore = ReservedWord::kBreak;   // No underscore necessary.
  std::ignore = ReservedWord::FOR;      // No underscore necessary.
  std::ignore = ReservedWord::kFor;     // No underscore necessary.
  std::ignore = ReservedWord::DO;       // No underscore necessary.
  std::ignore = ReservedWord::kDo;      // No underscore necessary.

  // Instantiate an extremely degenerately named set of nested types in order to
  // make sure that name conflicts with the codegen internals are properly
  // prevented.
  std::ignore = Function::Message{
      .description =
          Function::Message_::Message{
              .content = "multiplication (mod 5)",
          },
      .domain_field = Function::Fields_::INTEGERS_MOD_5,
      .codomain_field = Function::Fields_::INTEGERS_MOD_5,
  };

  // Check for expected values of `enum class Function::Fields`:
  std::ignore = Function::Fields::kDescription;
  std::ignore = Function::Fields::kDomainField;
  std::ignore = Function::Fields::kCodomainField;

  // Check for expected values of `enum class Function::Message_::Fields`:
  std::ignore = Function::Message_::Fields::kContent;

  // Check for expected values of `enum class Function::Fields_`:
  std::ignore = Function::Fields_::NONE;
  std::ignore = Function::Fields_::kNone;
  std::ignore = Function::Fields_::COMPLEX_NUMBERS;
  std::ignore = Function::Fields_::kComplexNumbers;
  std::ignore = Function::Fields_::INTEGERS_MOD_5;
  std::ignore = Function::Fields_::kIntegersMod5;
  std::ignore = Function::Fields_::MEROMORPHIC_FUNCTIONS_ON_COMPLEX_PLANE;
  std::ignore = Function::Fields_::kMeromorphicFunctionsOnComplexPlane;
  std::ignore = Function::Fields_::OTHER;
  std::ignore = Function::Fields_::kOther;
}

PW_MODIFY_DIAGNOSTICS_POP();

TEST(CodegenMessage, SetEncoder) {
  Pigweed::Message msg{};

  EXPECT_FALSE(msg.id);
  msg.id.SetEncoder(
      [](Pigweed::StreamEncoder&) -> Status { return OkStatus(); });
  EXPECT_TRUE(msg.id);
}

TEST(CodegenMessage, SetDecoder) {
  Pigweed::Message msg{};

  EXPECT_FALSE(msg.id);
  msg.id.SetDecoder(
      [](Pigweed::StreamDecoder&) -> Status { return OkStatus(); });
  EXPECT_TRUE(msg.id);
}

TEST(CodegenMessage, SetEncoderAndDecoder) {
  Pigweed::Message msg{};

  EXPECT_FALSE(msg.id);
  msg.id.SetEncoder(
      [](Pigweed::StreamEncoder&) -> Status { return OkStatus(); });
  msg.id.SetDecoder(
      [](Pigweed::StreamDecoder&) -> Status { return OkStatus(); });
  EXPECT_TRUE(msg.id);
}

TEST(CodegenMessage, SetEncoderAndDecoderMutableLambda) {
  Pigweed::Message msg{};

  EXPECT_FALSE(msg.id);
  int calls = 0;
  msg.id.SetEncoder([&calls](Pigweed::StreamEncoder&) mutable {
    calls += 1;
    return OkStatus();
  });
  msg.id.SetDecoder([&calls](Pigweed::StreamDecoder&) mutable {
    calls += 1;
    return OkStatus();
  });
  EXPECT_TRUE(msg.id);
}

TEST(CodegenMessage, Read) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.magic_number
    0x08, 0x49,
    // pigweed.ziggy
    0x10, 0xdd, 0x01,
    // pigweed.cycles
    0x19, 0xde, 0xad, 0xca, 0xfe, 0x10, 0x20, 0x30, 0x40,
    // pigweed.ratio
    0x25, 0x8f, 0xc2, 0xb5, 0xbf,
    // pigweed.error_message
    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
    // pigweed.bin
    0x40, 0x01,
    // pigweed.bungle
    0x70, 0x91, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  Pigweed::Message message{};
  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, OkStatus());

  constexpr std::string_view kExpectedErrorMessage{"not a typewriter"};

  EXPECT_EQ(message.magic_number, 0x49u);
  EXPECT_EQ(message.ziggy, -111);
  EXPECT_EQ(message.cycles, 0x40302010fecaaddeu);
  EXPECT_EQ(message.ratio, -1.42f);
  EXPECT_EQ(message.error_message.size(), kExpectedErrorMessage.size());
  EXPECT_EQ(std::memcmp(message.error_message.data(),
                        kExpectedErrorMessage.data(),
                        kExpectedErrorMessage.size()),
            0);
  EXPECT_EQ(message.bin, Pigweed::Protobuf::Binary::ZERO);
  EXPECT_EQ(message.bungle, -111);
}

TEST(CodegenMessage, ReadNonPackedScalar) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x08, 0x00,
    0x08, 0x10,
    0x08, 0x20,
    0x08, 0x30,
    // fixed32s[]. v={0, 16, 32, 48}
    0x35, 0x00, 0x00, 0x00, 0x00,
    0x35, 0x10, 0x00, 0x00, 0x00,
    0x35, 0x20, 0x00, 0x00, 0x00,
    0x35, 0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  ASSERT_EQ(message.uint32s.size(), 4u);
  for (unsigned short i = 0; i < 4; ++i) {
    EXPECT_EQ(message.uint32s[i], i * 16u);
  }

  ASSERT_EQ(message.fixed32s.size(), 4u);
  for (unsigned short i = 0; i < 4; ++i) {
    EXPECT_EQ(message.fixed32s[i], i * 16u);
  }
}

TEST(CodegenMessage, ReadPackedScalar) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x0a, 0x04,
    0x00,
    0x10,
    0x20,
    0x30,
    // fixed32s[]. v={0, 16, 32, 48}
    0x32, 0x10,
    0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x20, 0x00, 0x00, 0x00,
    0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  ASSERT_EQ(message.uint32s.size(), 4u);
  for (unsigned short i = 0; i < 4; ++i) {
    EXPECT_EQ(message.uint32s[i], i * 16u);
  }

  ASSERT_EQ(message.fixed32s.size(), 4u);
  for (unsigned short i = 0; i < 4; ++i) {
    EXPECT_EQ(message.fixed32s[i], i * 16u);
  }
}

TEST(CodegenMessage, ReadPackedScalarRepeated) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x0a, 0x04,
    0x00,
    0x10,
    0x20,
    0x30,
    // uint32s[], v={64, 80, 96, 112}
    0x0a, 0x04,
    0x40,
    0x50,
    0x60,
    0x70,
    // fixed32s[]. v={0, 16, 32, 48}
    0x32, 0x10,
    0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x20, 0x00, 0x00, 0x00,
    0x30, 0x00, 0x00, 0x00,
    // fixed32s[]. v={64, 80, 96, 112}
    0x32, 0x10,
    0x40, 0x00, 0x00, 0x00,
    0x50, 0x00, 0x00, 0x00,
    0x60, 0x00, 0x00, 0x00,
    0x70, 0x00, 0x00, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  ASSERT_EQ(message.uint32s.size(), 8u);
  for (unsigned short i = 0; i < 8; ++i) {
    EXPECT_EQ(message.uint32s[i], i * 16u);
  }

  ASSERT_EQ(message.fixed32s.size(), 8u);
  for (unsigned short i = 0; i < 8; ++i) {
    EXPECT_EQ(message.fixed32s[i], i * 16u);
  }
}

TEST(CodegenMessage, ReadPackedScalarExhausted) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint32s[], v={0, 16, 32, 48, 64, 80, 96, 112, 128}
    0x0a, 0x09,
    0x00,
    0x10,
    0x20,
    0x30,
    0x40,
    0x50,
    0x60,
    0x70,
    0x80,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  // uint32s has max_size=8, so this will exhaust the vector.
  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, Status::ResourceExhausted());
}

TEST(CodegenMessage, ReadPackedScalarCallback) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // sint32s[], v={-25, -1, 0, 1, 25}
    0x12, 0x05,
    0x31,
    0x01,
    0x00,
    0x02,
    0x32,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  // sint32s is a repeated field declared without max_count, so requirses a
  // callback to be decoded.
  RepeatedTest::Message message{};
  message.sint32s.SetDecoder([](RepeatedTest::StreamDecoder& decoder) {
    EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kSint32s);

    pw::Vector<int32_t, 8> sint32s{};
    const auto status = decoder.ReadSint32s(sint32s);
    EXPECT_EQ(status, OkStatus());

    EXPECT_EQ(sint32s.size(), 5u);
    EXPECT_EQ(sint32s[0], -25);
    EXPECT_EQ(sint32s[1], -1);
    EXPECT_EQ(sint32s[2], 0);
    EXPECT_EQ(sint32s[3], 1);
    EXPECT_EQ(sint32s[4], 25);

    return status;
  });

  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());
}

TEST(CodegenMessage, ReadPackedScalarFixedLength) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint64s[], v={1000, 2000, 3000, 4000}
    0x42, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
    // doubles[], v={3.14159, 2.71828}
    0x22, 0x10,
    0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
    0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  EXPECT_EQ(message.uint64s[0], 1000u);
  EXPECT_EQ(message.uint64s[1], 2000u);
  EXPECT_EQ(message.uint64s[2], 3000u);
  EXPECT_EQ(message.uint64s[3], 4000u);

  EXPECT_EQ(message.doubles[0], 3.14159);
  EXPECT_EQ(message.doubles[1], 2.71828);
}

TEST(CodegenMessage, ReadPackedScalarFixedLengthShort) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint64s[], v={1000, 2000}
    0x42, 0x04, 0xe8, 0x07, 0xd0, 0x0f,
    // doubles[], v={3.14159}
    0x22, 0x08,
    0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  EXPECT_EQ(message.uint64s[0], 1000u);
  EXPECT_EQ(message.uint64s[1], 2000u);
  EXPECT_EQ(message.uint64s[2], 0u);
  EXPECT_EQ(message.uint64s[3], 0u);

  EXPECT_EQ(message.doubles[0], 3.14159);
  EXPECT_EQ(message.doubles[1], 0);
}

TEST(CodegenMessage, ReadPackedScalarVarintFixedLengthExhausted) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // uint64s[], v={0, 1000, 2000, 3000, 4000}
    0x42, 0x09, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, Status::ResourceExhausted());
}

TEST(CodegenMessage, ReadPackedScalarFixedLengthExhausted) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // doubles[], v={3.14159, 2.71828, 1.41429, 1.73205}
    0x22, 0x20,
    0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
    0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
    0x1b, 0xf5, 0x10, 0x8d, 0xee, 0xa0, 0xf6, 0x3f,
    0xbc, 0x96, 0x90, 0x0f, 0x7a, 0xb6, 0xfb, 0x3f,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, Status::ResourceExhausted());
}

TEST(CodegenMessage, ReadPackedEnum) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // enums[], v={RED, GREEN, AMBER, RED}
    0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  ASSERT_EQ(message.enums.size(), 4u);
  for (unsigned short i = 0; i < 4; ++i) {
    EXPECT_TRUE(IsValidEnum(message.enums[i]));
  }

  EXPECT_EQ(message.enums[0], Enum::RED);
  EXPECT_EQ(message.enums[1], Enum::GREEN);
  EXPECT_EQ(message.enums[2], Enum::AMBER);
  EXPECT_EQ(message.enums[3], Enum::RED);
}

TEST(CodegenMessage, ReadStringExhausted) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.error_message
    0x2a, 0xd3, 0x01, 'T', 'h', 'i', 's', ' ', 'l', 'a', 'b', 'e', 'l', ' ', 'i',
    's', ' ', 't', 'h', 'e', ' ', 't', 'a', 'r', 'g', 'e', 't', ' ', 'o', 'f',
    ' ', 'a', ' ', 'g', 'o', 't', 'o', ' ', 'f', 'r', 'o', 'm', ' ', 'o', 'u',
    't', 's', 'i', 'd', 'e', ' ', 'o', 'f', ' ', 't', 'h', 'e', ' ', 'b', 'l',
    'o', 'c', 'k', ' ', 'c', 'o', 'n', 't', 'a', 'i', 'n', 'i', 'n', 'g', ' ',
    't', 'h', 'i', 's', ' ', 'l', 'a', 'b', 'e', 'l', ' ', 'A', 'N', 'D', ' ',
    't', 'h', 'i', 's', ' ', 'b', 'l', 'o', 'c', 'k', ' ', 'h', 'a', 's', ' ',
    'a', 'n', ' ', 'a', 'u', 't', 'o', 'm', 'a', 't', 'i', 'c', ' ', 'v', 'a',
    'r', 'i', 'a', 'b', 'l', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'a', 'n', ' ',
    'i', 'n', 'i', 't', 'i', 'a', 'l', 'i', 'z', 'e', 'r', ' ', 'A', 'N', 'D',
    ' ', 'y', 'o', 'u', 'r', ' ', 'w', 'i', 'n', 'd', 'o', 'w', ' ', 'w', 'a',
    's', 'n', '\'', 't', ' ', 'w', 'i', 'd', 'e', ' ', 'e', 'n', 'o', 'u', 'g',
    'h', ' ', 't', 'o', ' ', 'r', 'e', 'a', 'd', ' ', 't', 'h', 'i', 's', ' ',
    'w', 'h', 'o', 'l', 'e', ' ', 'e', 'r', 'r', 'o', 'r', ' ', 'm', 'e', 's',
    's', 'a', 'g', 'e'
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  Pigweed::Message message{};
  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, Status::ResourceExhausted());
}

TEST(CodegenMessage, ReadStringCallback) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.description
    0x62, 0x5c, 'a', 'n', ' ', 'o', 'p', 'e', 'n', ' ', 's', 'o', 'u', 'r', 'c',
    'e', ' ', 'c', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', ' ', 'o', 'f',
    ' ', 'e', 'm', 'b', 'e', 'd', 'd', 'e', 'd', '-', 't', 'a', 'r', 'g', 'e',
    't', 'e', 'd', ' ', 'l', 'i', 'b', 'r', 'a', 'r', 'i', 'e', 's', '-', 'o',
    'r', ' ', 'a', 's', ' ', 'w', 'e', ' ', 'l', 'i', 'k', 'e', ' ', 't', 'o',
    ' ', 'c', 'a', 'l', 'l', ' ', 't', 'h', 'e', 'm', ',', ' ', 'm', 'o', 'd',
    'u', 'l', 'e', 's'
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  // pigweed.description has no max_size specified so a callback must be
  // set to read the value if present.
  Pigweed::Message message{};
  message.description.SetDecoder([](Pigweed::StreamDecoder& decoder) {
    EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kDescription);

    constexpr std::string_view kExpectedDescription{
        "an open source collection of embedded-targeted libraries-or as we "
        "like to call them, modules"};

    std::array<char, 128> description{};
    const auto sws = decoder.ReadDescription(description);
    EXPECT_EQ(sws.status(), OkStatus());
    EXPECT_EQ(sws.size(), kExpectedDescription.size());
    EXPECT_EQ(std::memcmp(description.data(),
                          kExpectedDescription.data(),
                          kExpectedDescription.size()),
              0);

    return sws.status();
  });

  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, OkStatus());
}

TEST(CodegenMessage, ReadMultipleString) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.error_message
    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
    // pigweed.error_message
    0x02a, 0x07, 'o', 'n', ' ', 'f', 'i', 'r', 'e'
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  Pigweed::Message message{};
  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, OkStatus());

  constexpr std::string_view kExpectedErrorMessage{"on fire"};

  EXPECT_EQ(message.error_message.size(), kExpectedErrorMessage.size());
  EXPECT_EQ(std::memcmp(message.error_message.data(),
                        kExpectedErrorMessage.data(),
                        kExpectedErrorMessage.size()),
            0);
}

TEST(CodegenMessage, ReadRepeatedStrings) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // repeated.strings
    0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
    't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
    'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
    // repeated.strings
    0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
    's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
    ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
    // repeated.strings
    0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
    'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
    ' ', 's', 'o', ' ', 'd', 'i', 'e',
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  // Repeated strings require a callback to avoid forcing multi-dimensional
  // arrays upon the caller.
  RepeatedTest::Message message{};
  int i = 0;
  message.strings.SetDecoder([&i](RepeatedTest::StreamDecoder& decoder) {
    EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStrings);

    constexpr std::string_view kExpectedStrings[] = {
        {"if music be the food of love, play on"},
        {"give me excess of it, that, surfeiting"},
        {"the appetite may sicken, and so die"}};

    std::array<char, 40> strings{};
    const StatusWithSize sws = decoder.ReadStrings(strings);
    EXPECT_EQ(sws.status(), OkStatus());
    EXPECT_EQ(sws.size(), kExpectedStrings[i].size());
    EXPECT_EQ(std::memcmp(strings.data(),
                          kExpectedStrings[i].data(),
                          kExpectedStrings[i].size()),
              0);

    ++i;
    return sws.status();
  });

  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());
}

TEST(CodegenMessage, ReadForcedCallback) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.special_property
    0x68, 0x2a,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  // pigweed.special_property has use_callback=true to force the use of a
  // callback even though it's a simple scalar.
  Pigweed::Message message{};
  message.special_property.SetDecoder([](Pigweed::StreamDecoder& decoder) {
    EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kSpecialProperty);

    pw::Result<uint32_t> result = decoder.ReadSpecialProperty();
    EXPECT_EQ(result.status(), OkStatus());
    EXPECT_EQ(result.value(), 42u);

    return result.status();
  });
  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, OkStatus());
}

TEST(CodegenMessage, ReadMissingCallback) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // repeated.strings
    0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
    't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
    'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
    // repeated.strings
    0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
    's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
    ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
    // repeated.strings
    0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
    'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
    ' ', 's', 'o', ' ', 'd', 'i', 'e',
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  // Fields with unset callbacks are ignored in the decode operation.
  RepeatedTest::Message message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());
}

TEST(CodegenMessage, ReadFixedLength) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.data
    0x5a, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  Pigweed::Message message{};
  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, OkStatus());

  EXPECT_EQ(message.data[0], std::byte{0x01});
  EXPECT_EQ(message.data[1], std::byte{0x02});
  EXPECT_EQ(message.data[2], std::byte{0x03});
  EXPECT_EQ(message.data[3], std::byte{0x04});
  EXPECT_EQ(message.data[4], std::byte{0x05});
  EXPECT_EQ(message.data[5], std::byte{0x06});
  EXPECT_EQ(message.data[6], std::byte{0x07});
  EXPECT_EQ(message.data[7], std::byte{0x08});
}

TEST(CodegenMessage, ReadFixedLengthShort) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.data
    0x5a, 0x04, 0x01, 0x02, 0x03, 0x04
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  Pigweed::Message message{};
  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, OkStatus());

  EXPECT_EQ(message.data[0], std::byte{0x01});
  EXPECT_EQ(message.data[1], std::byte{0x02});
  EXPECT_EQ(message.data[2], std::byte{0x03});
  EXPECT_EQ(message.data[3], std::byte{0x04});
  // Remaining bytes are whatever you initialized them to.
  EXPECT_EQ(message.data[4], std::byte{0x00});
  EXPECT_EQ(message.data[5], std::byte{0x00});
  EXPECT_EQ(message.data[6], std::byte{0x00});
  EXPECT_EQ(message.data[7], std::byte{0x00});
}

TEST(CodegenMessage, ReadFixedLengthExhausted) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.data
    0x5a, 0x0c, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
    0x09, 0x0a, 0x0b, 0x0c
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  Pigweed::Message message{};
  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, Status::ResourceExhausted());
}

TEST(CodegenMessage, ReadNested) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.magic_number
    0x08, 0x49,
    // pigweed.pigweed
    0x3a, 0x02,
    // pigweed.pigweed.status
    0x08, 0x02,
    // pigweed.ziggy
    0x10, 0xdd, 0x01,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  Pigweed::Message message{};
  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, OkStatus());

  EXPECT_EQ(message.magic_number, 0x49u);
  EXPECT_EQ(message.pigweed.status, Bool::FILE_NOT_FOUND);
  EXPECT_EQ(message.ziggy, -111);
}

TEST(CodegenMessage, ReadNestedImported) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // period.start
    0x0a, 0x08,
    // period.start.seconds v=1517949900
    0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05,
    // period.start.nanoseconds v=0
    0x10, 0x00,
    // period.end
    0x12, 0x08,
    // period.end.seconds, v=1517950378
    0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05,
    // period.end.nanoseconds, v=0
    0x10, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Period::StreamDecoder period(reader);

  // Messages imported from another file can be directly embedded in a message.
  Period::Message message{};
  const auto status = period.Read(message);
  ASSERT_EQ(status, OkStatus());

  EXPECT_EQ(message.start.seconds, 1517949900u);
  EXPECT_EQ(message.start.nanoseconds, 0u);
  EXPECT_EQ(message.end.seconds, 1517950378u);
  EXPECT_EQ(message.end.nanoseconds, 0u);
}

TEST(CodegenMessage, ReadNestedRepeated) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // repeated.structs
    0x2a, 0x04,
    // repeated.structs.one v=16
    0x08, 0x10,
    // repeated.structs.two v=32
    0x10, 0x20,
    // repeated.structs
    0x2a, 0x04,
    // repeated.structs.one v=48
    0x08, 0x30,
    // repeated.structs.two v=64
    0x10, 0x40,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  // Repeated nested messages require a callback since there would otherwise be
  // no way to set callbacks on the nested message.
  RepeatedTest::Message message{};
  unsigned i = 0;
  message.structs.SetDecoder([&i](RepeatedTest::StreamDecoder& decoder) {
    EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStructs);

    Struct::Message structs_message{};
    auto structs_decoder = decoder.GetStructsDecoder();
    const auto status = structs_decoder.Read(structs_message);
    EXPECT_EQ(status, OkStatus());

    EXPECT_LT(i, 2u);
    EXPECT_EQ(structs_message.one, i * 32 + 16u);
    EXPECT_EQ(structs_message.two, i * 32 + 32u);
    ++i;

    return status;
  });

  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());
}

TEST(CodegenMessage, ReadNestedForcedCallback) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // pigweed.device_info
    0x32, 0x0e,
    // pigweed.device_info.device_name
    0x0a, 0x05, 'p', 'i', 'x', 'e', 'l',
    // pigweed.device_info.device_id
    0x15, 0x08, 0x08, 0x08, 0x08,
    // pigweed.device_info.status
    0x18, 0x00,
      };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  Pigweed::StreamDecoder pigweed(reader);

  // pigweed.device_info has use_callback=true to force the use of a callback.
  Pigweed::Message message{};
  message.device_info.SetDecoder([](Pigweed::StreamDecoder& decoder) {
    EXPECT_EQ(decoder.Field().value(), Pigweed::Fields::kDeviceInfo);

    DeviceInfo::Message device_info{};
    DeviceInfo::StreamDecoder device_info_decoder =
        decoder.GetDeviceInfoDecoder();
    const auto status = device_info_decoder.Read(device_info);
    EXPECT_EQ(status, OkStatus());

    constexpr std::string_view kExpectedDeviceName{"pixel"};

    EXPECT_EQ(device_info.device_name.size(), kExpectedDeviceName.size());
    EXPECT_EQ(std::memcmp(device_info.device_name.data(),
                          kExpectedDeviceName.data(),
                          kExpectedDeviceName.size()),
              0);
    EXPECT_EQ(device_info.device_id, 0x08080808u);
    EXPECT_EQ(device_info.status, DeviceInfo::DeviceStatus::OK);

    return status;
  });
  const auto status = pigweed.Read(message);
  ASSERT_EQ(status, OkStatus());
}

TEST(CodegenMessage, ReadOptionalPresent) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // optional.sometimes_present_fixed
    0x0d, 0x2a, 0x00, 0x00, 0x00,
    // optional.sometimes_present_varint
    0x10, 0x2a,
    // optional.explicitly_present_fixed
    0x1d, 0x45, 0x00, 0x00, 0x00,
    // optional.explicitly_present_varint
    0x20, 0x45,
    // optional.sometimes_empty_fixed
    0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
    // optional.sometimes_empty_varint
    0x32, 0x01, 0x63,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  OptionalTest::StreamDecoder optional_test(reader);

  OptionalTest::Message message{};
  const auto status = optional_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  EXPECT_EQ(message.sometimes_present_fixed, 0x2a);
  EXPECT_EQ(message.sometimes_present_varint, 0x2a);
  EXPECT_TRUE(message.explicitly_present_fixed);
  EXPECT_EQ(*message.explicitly_present_fixed, 0x45);
  EXPECT_TRUE(message.explicitly_present_varint);
  EXPECT_EQ(*message.explicitly_present_varint, 0x45);
  EXPECT_FALSE(message.sometimes_empty_fixed.empty());
  EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
  EXPECT_EQ(message.sometimes_empty_fixed[0], 0x63);
  EXPECT_FALSE(message.sometimes_empty_varint.empty());
  EXPECT_EQ(message.sometimes_empty_varint.size(), 1u);
  EXPECT_EQ(message.sometimes_empty_varint[0], 0x63);
}

TEST(CodegenMessage, ReadOptionalNotPresent) {
  constexpr std::array<std::byte, 0> proto_data{};

  stream::MemoryReader reader(proto_data);
  OptionalTest::StreamDecoder optional_test(reader);

  OptionalTest::Message message{};
  const auto status = optional_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  // Non-optional fields have their default value.
  EXPECT_EQ(message.sometimes_present_fixed, 0);
  EXPECT_EQ(message.sometimes_present_varint, 0);
  EXPECT_TRUE(message.sometimes_empty_fixed.empty());
  EXPECT_TRUE(message.sometimes_empty_varint.empty());

  // Optional fields are explicitly not present.
  EXPECT_FALSE(message.explicitly_present_fixed);
  EXPECT_FALSE(message.explicitly_present_varint);
}

TEST(CodegenMessage, ReadOptionalPresentDefaults) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // optional.sometimes_present_fixed
    0x0d, 0x00, 0x00, 0x00, 0x00,
    // optional.sometimes_present_varint
    0x10, 0x00,
    // optional.explicitly_present_fixed
    0x1d, 0x00, 0x00, 0x00, 0x00,
    // optional.explicitly_present_varint
    0x20, 0x00,
    // optional.sometimes_empty_fixed
    0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
    // optional.sometimes_empty_varint
    0x32, 0x01, 0x00,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  OptionalTest::StreamDecoder optional_test(reader);

  OptionalTest::Message message{};
  const auto status = optional_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  // Non-optional fields have their default value and aren't meaningfully
  // different from missing.
  EXPECT_EQ(message.sometimes_present_fixed, 0x00);
  EXPECT_EQ(message.sometimes_present_varint, 0x00);

  // Optional fields are explicitly present with a default value.
  EXPECT_TRUE(message.explicitly_present_fixed);
  EXPECT_EQ(*message.explicitly_present_fixed, 0x00);
  EXPECT_TRUE(message.explicitly_present_varint);
  EXPECT_EQ(*message.explicitly_present_varint, 0x00);

  // Repeated fields with a default value are meaningfully non-empty.
  EXPECT_FALSE(message.sometimes_empty_fixed.empty());
  EXPECT_EQ(message.sometimes_empty_fixed.size(), 1u);
  EXPECT_EQ(message.sometimes_empty_fixed[0], 0x00);
  EXPECT_FALSE(message.sometimes_empty_varint.empty());
  EXPECT_EQ(message.sometimes_empty_varint.size(), 1u);
  EXPECT_EQ(message.sometimes_empty_varint[0], 0x00);
}

TEST(CodegenMessage, DefaultValues) {
  Pigweed::Message message;
  EXPECT_EQ(message.magic_number, 0u);
  EXPECT_EQ(message.ziggy, 0);
  EXPECT_EQ(message.cycles, 0u);
  EXPECT_EQ(message.ratio, 0.0);
  EXPECT_EQ(message.error_message, "");
  EXPECT_EQ(message.pigweed.status, Bool::kTrue);
  EXPECT_EQ(message.bin, Pigweed::Protobuf::Binary::kOne);
  EXPECT_EQ(message.bungle, 0);
  EXPECT_EQ(message.test_sfixed32, 0);
  EXPECT_EQ(message.test_sfixed64, 0);
}

TEST(CodegenMessage, ReadImportedOptions) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // notice
    0x0a, 0x0f,
    // notice.message
    0x0a, 0x0d, 'P', 'r', 'e', 's', 's', ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y'
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  TestMessage::StreamDecoder test_message(reader);

  // The options file for the imported proto is applied, making the string
  // field a vector rather than requiring a callback.
  TestMessage::Message message{};
  const auto status = test_message.Read(message);
  ASSERT_EQ(status, OkStatus());

  constexpr std::string_view kExpectedMessage{"Press any key"};

  EXPECT_EQ(message.notice.message.size(), kExpectedMessage.size());
  EXPECT_EQ(std::memcmp(message.notice.message.data(),
                        kExpectedMessage.data(),
                        kExpectedMessage.size()),
            0);
}

TEST(CodegenMessage, ReadImportedFromDepsOptions) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // debug
    0x12, 0x0f,
    // debug.message
    0x0a, 0x0d, 'P', 'r', 'e', 's', 's', ' ', 'a', 'n', 'y', ' ', 'k', 'e', 'y'
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  TestMessage::StreamDecoder test_message(reader);

  // The options file for the imported proto is applied, making the string
  // fields a vector rather than requiring a callback. This will not compile if
  // the .pwpb_options files aren't applied correctly.
  TestMessage::Message message{};
  const auto status = test_message.Read(message);
  ASSERT_EQ(status, OkStatus());

  constexpr std::string_view kExpectedMessage{"Press any key"};

  EXPECT_EQ(message.debug.message.size(), kExpectedMessage.size());
  EXPECT_EQ(std::memcmp(message.debug.message.data(),
                        kExpectedMessage.data(),
                        kExpectedMessage.size()),
            0);
  EXPECT_EQ(message.prefix_debug.message.size(), 0U);
}

class BreakableDecoder : public KeyValuePair::StreamDecoder {
 public:
  constexpr BreakableDecoder(stream::Reader& reader) : StreamDecoder(reader) {}

  Status Read(KeyValuePair::Message& message,
              span<const internal::MessageField> table) {
    return ::pw::protobuf::StreamDecoder::Read(
        as_writable_bytes(span(&message, 1)), table);
  }
};

TEST(CodegenMessage, DISABLED_ReadDoesNotOverrun) {
  // Deliberately construct a message table that attempts to violate the bounds
  // of the structure. We're not testing that a developer can't do this, rather
  // that the protobuf decoder can't be exploited in this way.
  constexpr internal::MessageField kMessageFields[] = {
      {1,
       WireType::kDelimited,
       sizeof(std::byte),
       static_cast<internal::VarintType>(0),
       false,
       false,
       false,
       false,
       internal::CallbackType::kNone,
       0,
       sizeof(KeyValuePair::Message) * 2,
       {}},
  };

  // clang-format off
  constexpr uint8_t proto_data[] = {
    // id=1, len=9,
    0x0a, 0x08, 'd', 'o', 'n', 't', 'e', 'a', 't', 'm', 'e',
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  BreakableDecoder decoder(reader);

  KeyValuePair::Message message{};
  // ASSERT_CRASH
  std::ignore = decoder.Read(message, kMessageFields);
}

TEST(CodegenMessage, Write) {
  constexpr uint8_t pigweed_data[] = {
      0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80};

  Pigweed::Message message{};
  message.magic_number = 0x49u;
  message.ziggy = -111;
  message.cycles = 0x40302010fecaaddeu;
  message.ratio = -1.42f;
  message.error_message = "not a typewriter";
  message.pigweed.status = Bool::FILE_NOT_FOUND;
  message.bin = Pigweed::Protobuf::Binary::ZERO;
  message.bungle = -111;
  message.proto.bin = Proto::Binary::OFF;
  message.proto.pigweed_pigweed_bin = Pigweed::Pigweed::Binary::ZERO;
  message.proto.pigweed_protobuf_bin = Pigweed::Protobuf::Binary::ZERO;
  message.proto.meta.file_name = "/etc/passwd";
  message.proto.meta.status = Pigweed::Protobuf::Compiler::Status::FUBAR;
  message.proto.meta.protobuf_bin = Pigweed::Protobuf::Binary::ONE;
  message.proto.meta.pigweed_bin = Pigweed::Pigweed::Binary::ONE;
  std::memcpy(message.data.data(), pigweed_data, sizeof(pigweed_data));

  // No callback fields are being written, so the max size without values is
  // sufficient.
  std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytesWithoutValues];
  std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];

  stream::MemoryWriter writer(encode_buffer);
  Pigweed::StreamEncoder pigweed(writer, temp_buffer);

  const auto status = pigweed.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // pigweed.magic_number
    0x08, 0x49,
    // pigweed.ziggy
    0x10, 0xdd, 0x01,
    // pigweed.cycles
    0x19, 0xde, 0xad, 0xca, 0xfe, 0x10, 0x20, 0x30, 0x40,
    // pigweed.ratio
    0x25, 0x8f, 0xc2, 0xb5, 0xbf,
    // pigweed.error_message
    0x2a, 0x10, 'n', 'o', 't', ' ', 'a', ' ',
    't', 'y', 'p', 'e', 'w', 'r', 'i', 't', 'e', 'r',
    // pigweed.pigweed
    0x3a, 0x02,
    // pigweed.pigweed.status
    0x08, 0x02,
    // pigweed.bin
    0x40, 0x01,
    // pigweed.proto
    0x4a, 0x15,
    // pigweed.proto.pigweed_protobuf_bin
    0x20, 0x01,
    // pigweed.proto.meta
    0x2a, 0x11,
    // pigweed.proto.meta.file_name
    0x0a, 0x0b, '/', 'e', 't', 'c', '/', 'p', 'a', 's', 's', 'w', 'd',
    // pigweed.proto.meta.status
    0x10, 0x02,
    // pigweed.proto.meta.pigweed_bin
    0x20, 0x01,
    // pigweed.bytes
    0x5a, 0x08, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80,
    // pigweed.bungle
    0x70, 0x91, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WriteDefaults) {
  Pigweed::Message message{};

  std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytesWithoutValues];
  std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];

  stream::MemoryWriter writer(encode_buffer);
  Pigweed::StreamEncoder pigweed(writer, temp_buffer);

  const auto status = pigweed.Write(message);
  ASSERT_EQ(status, OkStatus());

  // Since all fields are at their default, the output should be zero sized.
  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), 0u);
}

TEST(CodegenMessage, WritePackedScalar) {
  RepeatedTest::Message message{};
  for (unsigned i = 0; i < 4; ++i) {
    message.uint32s.push_back(i * 16u);
    message.fixed32s.push_back(i * 16u);
  }

  // RepeatedTest has several different repeated fields, some of which have a
  // static size while others don't. `uint32s`  and `fixed32` have maximum
  // static sizes, so the max encoded size accounts for them.
  std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytesWithoutValues];

  stream::MemoryWriter writer(encode_buffer);
  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());

  const auto status = repeated_test.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // uint32s[], v={0, 16, 32, 48}
    0x0a, 0x04,
    0x00,
    0x10,
    0x20,
    0x30,
    // fixed32s[]. v={0, 16, 32, 48}
    0x32, 0x10,
    0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x20, 0x00, 0x00, 0x00,
    0x30, 0x00, 0x00, 0x00,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WritePackedScalarFixedLength) {
  RepeatedTest::Message message{};
  for (unsigned i = 0; i < 4; ++i) {
    message.uint64s[i] = (i + 1) * 1000u;
  }
  message.doubles[0] = 3.14159;
  message.doubles[1] = 2.71828;

  // RepeatedTest has several different repeated fields, some of which have a
  // static size while others don't. `doubles` has a fixed length, so the max
  // encoded size accounts for it.
  std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytesWithoutValues];

  stream::MemoryWriter writer(encode_buffer);
  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());

  const auto status = repeated_test.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // doubles[], v={3.14159, 2.71828}
    0x22, 0x10,
    0x6e, 0x86, 0x1b, 0xf0, 0xf9, 0x21, 0x09, 0x40,
    0x90, 0xf7, 0xaa, 0x95, 0x09, 0xbf, 0x05, 0x40,
    // uint64s[], v={1000, 2000, 3000, 4000}
    0x42, 0x08, 0xe8, 0x07, 0xd0, 0x0f, 0xb8, 0x17, 0xa0, 0x1f,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WritePackedScalarCallback) {
  RepeatedTest::Message message{};
  message.sint32s.SetEncoder([](RepeatedTest::StreamEncoder& encoder) {
    constexpr int32_t sint32s[] = {-25, -1, 0, 1, 25};
    return encoder.WriteSint32s(sint32s);
  });

  std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytesWithoutValues +
                          varint::kMaxVarint32SizeBytes * 5];

  stream::MemoryWriter writer(encode_buffer);
  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());

  const auto status = repeated_test.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // sint32s[], v={-25, -1, 0, 1, 25}
    0x12, 0x05,
    0x31,
    0x01,
    0x00,
    0x02,
    0x32,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WritePackedEnum) {
  RepeatedTest::Message message{};
  message.enums.push_back(Enum::RED);
  message.enums.push_back(Enum::GREEN);
  message.enums.push_back(Enum::AMBER);
  message.enums.push_back(Enum::RED);

  // RepeatedTest has several different repeated fields, some of which have a
  // static size while others don't. `enums` is one of the fields which does
  // specify a static size option, so the max encoded size accounts for its
  // worst case length.
  std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytesWithoutValues];

  stream::MemoryWriter writer(encode_buffer);
  RepeatedTest::StreamEncoder repeated_test(writer, ByteSpan());

  const auto status = repeated_test.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // enums[], v={RED, GREEN, AMBER, RED}
    0x4a, 0x04, 0x00, 0x02, 0x01, 0x00,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WriteStringCallback) {
  Pigweed::Message message{};
  // pigweed.description has no max_size specified so a callback must be
  // set to write the value.
  message.description.SetEncoder([](Pigweed::StreamEncoder& encoder) {
    return encoder.WriteDescription(
        "an open source collection of embedded-targeted "
        "libraries-or as we like to call them, modules");
  });

  std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytesWithoutValues + 92];
  std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];

  stream::MemoryWriter writer(encode_buffer);
  Pigweed::StreamEncoder pigweed(writer, temp_buffer);

  const auto status = pigweed.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // pigweed.description
    0x62, 0x5c, 'a', 'n', ' ', 'o', 'p', 'e', 'n', ' ', 's', 'o', 'u', 'r', 'c',
    'e', ' ', 'c', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', ' ', 'o', 'f',
    ' ', 'e', 'm', 'b', 'e', 'd', 'd', 'e', 'd', '-', 't', 'a', 'r', 'g', 'e',
    't', 'e', 'd', ' ', 'l', 'i', 'b', 'r', 'a', 'r', 'i', 'e', 's', '-', 'o',
    'r', ' ', 'a', 's', ' ', 'w', 'e', ' ', 'l', 'i', 'k', 'e', ' ', 't', 'o',
    ' ', 'c', 'a', 'l', 'l', ' ', 't', 'h', 'e', 'm', ',', ' ', 'm', 'o', 'd',
    'u', 'l', 'e', 's',
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WriteForcedCallback) {
  Pigweed::Message message{};
  // pigweed.special_property has use_callback=true to force the use of a
  // callback even though it's a simple scalar.
  message.special_property.SetEncoder([](Pigweed::StreamEncoder& encoder) {
    return encoder.WriteSpecialProperty(42u);
  });

  // As the field is a scalar, it doesn't have an associated variable-length
  // value, so the max size without values is sufficient.
  std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytesWithoutValues];
  std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes];

  stream::MemoryWriter writer(encode_buffer);
  Pigweed::StreamEncoder pigweed(writer, temp_buffer);

  const auto status = pigweed.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // pigweed.special_property
    0x68, 0x2a,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WriteNestedImported) {
  Period::Message message{};
  message.start.seconds = 1517949900u;
  message.end.seconds = 1517950378u;

  std::byte encode_buffer[Period::kMaxEncodedSizeBytes];
  std::byte temp_buffer[Period::kScratchBufferSizeBytes];

  stream::MemoryWriter writer(encode_buffer);
  Period::StreamEncoder period(writer, temp_buffer);

  const auto status = period.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // period.start
    0x0a, 0x06,
    // period.start.seconds v=1517949900
    0x08, 0xcc, 0xa7, 0xe8, 0xd3, 0x05,
    // period.end
    0x12, 0x06,
    // period.end.seconds, v=1517950378
    0x08, 0xaa, 0xab, 0xe8, 0xd3, 0x05,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WriteNestedRepeated) {
  RepeatedTest::Message message{};
  // Repeated nested messages require a callback since there would otherwise be
  // no way to set callbacks on the nested message.
  message.structs.SetEncoder([](RepeatedTest::StreamEncoder& encoder) {
    for (uint32_t i = 0; i < 2; ++i) {
      Struct::Message struct_message{};
      struct_message.one = i * 32 + 16u;
      struct_message.two = i * 32 + 32u;

      const auto status = encoder.GetStructsEncoder().Write(struct_message);
      EXPECT_EQ(status, OkStatus());
    }
    return OkStatus();
  });

  std::byte encode_buffer[RepeatedTest::kMaxEncodedSizeBytesWithoutValues +
                          Struct::kMaxEncodedSizeBytes * 2];
  std::byte temp_buffer[RepeatedTest::kScratchBufferSizeBytes +
                        Struct::kMaxEncodedSizeBytes];

  stream::MemoryWriter writer(encode_buffer);
  RepeatedTest::StreamEncoder repeated_test(writer, temp_buffer);

  const auto status = repeated_test.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // repeated.structs
    0x2a, 0x04,
    // repeated.structs.one v=16
    0x08, 0x10,
    // repeated.structs.two v=32
    0x10, 0x20,
    // repeated.structs
    0x2a, 0x04,
    // repeated.structs.one v=48
    0x08, 0x30,
    // repeated.structs.two v=64
    0x10, 0x40,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WriteNestedForcedCallback) {
  // Manually add some additional buffer space for the values we know we want
  // to encode to the message.
  constexpr size_t kEncodedAttributesSize = 32;

  Pigweed::Message message{};
  // pigweed.device_info has use_callback=true to force the use of a callback.
  message.device_info.SetEncoder([](Pigweed::StreamEncoder& encoder) {
    DeviceInfo::Message device_info{};
    device_info.device_name = "pixel";
    device_info.device_id = 0x08080808u;
    device_info.status = DeviceInfo::DeviceStatus::OK;

    // Use the callback to set nested callbacks.
    device_info.attributes.SetEncoder(
        [](DeviceInfo::StreamEncoder& device_info_encoder) {
          KeyValuePair::Message attribute{};

          attribute.key = "version";
          attribute.value = "5.3.1";
          PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute));

          attribute.key = "chip";
          attribute.value = "left-soc";
          PW_TRY(device_info_encoder.GetAttributesEncoder().Write(attribute));

          return OkStatus();
        });

    return encoder.GetDeviceInfoEncoder().Write(device_info);
  });

  std::byte encode_buffer[Pigweed::kMaxEncodedSizeBytesWithoutValues +
                          DeviceInfo::kMaxEncodedSizeBytesWithoutValues +
                          kEncodedAttributesSize];
  std::byte temp_buffer[Pigweed::kScratchBufferSizeBytes +
                        DeviceInfo::kMaxEncodedSizeBytesWithoutValues +
                        kEncodedAttributesSize];

  stream::MemoryWriter writer(encode_buffer);
  Pigweed::StreamEncoder pigweed(writer, temp_buffer);

  const auto status = pigweed.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // pigweed.device_info
    0x32, 0x30,
    // pigweed.device_info.device_name
    0x0a, 0x05, 'p', 'i', 'x', 'e', 'l',
    // pigweed.device_info.device_id
    0x15, 0x08, 0x08, 0x08, 0x08,
    // pigweed.device_info.attributes[0]
    0x22, 0x10,
    // pigweed.device_info.attributes[0].key
    0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
    // pigweed.device_info.attributes[0].value
    0x12, 0x05, '5', '.', '3', '.', '1',
    // pigweed.device_info.attributes[1]
    0x22, 0x10,
    // pigweed.device_info.attributes[1].key
    0x0a, 0x04, 'c', 'h', 'i', 'p',
    // pigweed.device_info.attributes[1].value
    0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, EnumAliases) {
  // Unprefixed enum.
  EXPECT_EQ(Bool::kTrue, Bool::TRUE);
  EXPECT_EQ(Bool::kFalse, Bool::FALSE);
  EXPECT_EQ(Bool::kFileNotFound, Bool::FILE_NOT_FOUND);

  // Prefixed enum has the prefix removed.
  EXPECT_EQ(Error::kNone, Error::ERROR_NONE);
  EXPECT_EQ(Error::kNotFound, Error::ERROR_NOT_FOUND);
  EXPECT_EQ(Error::kUnknown, Error::ERROR_UNKNOWN);

  // Single-value enum.
  EXPECT_EQ(AlwaysBlue::kBlue, AlwaysBlue::BLUE);
}

TEST(CodegenMessage, WriteOptionalPresent) {
  OptionalTest::Message message{};
  message.sometimes_present_fixed = 0x2a;
  message.sometimes_present_varint = 0x2a;
  message.explicitly_present_fixed = 0x45;
  message.explicitly_present_varint = 0x45;
  message.sometimes_empty_fixed.push_back(0x63);
  message.sometimes_empty_varint.push_back(0x63);

  std::byte encode_buffer[512];

  stream::MemoryWriter writer(encode_buffer);
  OptionalTest::StreamEncoder optional_test(writer, ByteSpan());

  const auto status = optional_test.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // optional.sometimes_present_fixed
    0x0d, 0x2a, 0x00, 0x00, 0x00,
    // optional.sometimes_present_varint
    0x10, 0x2a,
    // optional.explicitly_present_fixed
    0x1d, 0x45, 0x00, 0x00, 0x00,
    // optional.explicitly_present_varint
    0x20, 0x45,
    // optional.sometimes_empty_fixed
    0x2a, 0x04, 0x63, 0x00, 0x00, 0x00,
    // optional.sometimes_empty_varint
    0x32, 0x01, 0x63,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

TEST(CodegenMessage, WriteOptionalNotPresent) {
  OptionalTest::Message message{};

  std::byte encode_buffer[512];

  stream::MemoryWriter writer(encode_buffer);
  OptionalTest::StreamEncoder optional_test(writer, ByteSpan());

  const auto status = optional_test.Write(message);
  ASSERT_EQ(status, OkStatus());

  // The expected proto is empty; no bytes should be written.

  ConstByteSpan result = writer.WrittenData();
  EXPECT_TRUE(result.empty());
}

TEST(CodegenMessage, WriteOptionalPresentDefaults) {
  OptionalTest::Message message{};
  // Non-optional fields with a default value are not explicitly encoded, so
  // aren't meaningfully different from one that's just ommitted.
  message.sometimes_present_fixed = 0x00;
  message.sometimes_present_varint = 0x00;
  // Optional fields, even with a default value, are explicitly encoded.
  message.explicitly_present_fixed = 0x00;
  message.explicitly_present_varint = 0x00;
  // Repeated fields with a default value are meaningfully non-empty.
  message.sometimes_empty_fixed.push_back(0x00);
  message.sometimes_empty_varint.push_back(0x00);

  std::byte encode_buffer[512];

  stream::MemoryWriter writer(encode_buffer);
  OptionalTest::StreamEncoder optional_test(writer, ByteSpan());

  const auto status = optional_test.Write(message);
  ASSERT_EQ(status, OkStatus());

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // optional.explicitly_present_fixed
    0x1d, 0x00, 0x00, 0x00, 0x00,
    // optional.explicitly_present_varint
    0x20, 0x00,
    // optional.sometimes_empty_fixed
    0x2a, 0x04, 0x00, 0x00, 0x00, 0x00,
    // optional.sometimes_empty_varint
    0x32, 0x01, 0x00,
  };
  // clang-format on

  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(expected_proto));
  EXPECT_EQ(std::memcmp(result.data(), expected_proto, sizeof(expected_proto)),
            0);
}

class BreakableEncoder : public KeyValuePair::MemoryEncoder {
 public:
  constexpr BreakableEncoder(ByteSpan buffer)
      : KeyValuePair::MemoryEncoder(buffer) {}

  Status Write(const KeyValuePair::Message& message,
               span<const internal::MessageField> table) {
    return ::pw::protobuf::StreamEncoder::Write(as_bytes(span(&message, 1)),
                                                table);
  }
};

TEST(CodegenMessage, DISABLED_WriteDoesNotOverrun) {
  // Deliberately construct a message table that attempts to violate the bounds
  // of the structure. We're not testing that a developer can't do this, rather
  // that the protobuf encoder can't be exploited in this way.
  constexpr internal::MessageField kMessageFields[] = {
      {1,
       WireType::kDelimited,
       sizeof(std::byte),
       static_cast<internal::VarintType>(0),
       false,
       false,
       false,
       false,
       internal::CallbackType::kNone,
       0,
       sizeof(KeyValuePair::Message) * 2,
       {}},
  };

  std::byte encode_buffer[64];

  BreakableEncoder encoder(encode_buffer);
  KeyValuePair::Message message{};
  // ASSERT_CRASH
  std::ignore = encoder.Write(message, kMessageFields);
}

// The following tests cover using the codegen struct Message and callbacks in
// different ways.

// Check that the callback function object is large enough to implement a
// "call a function on this" lambda.
class StringChecker {
 public:
  StringChecker() = default;
  ~StringChecker() = default;

  Status Check(RepeatedTest::StreamDecoder& repeated_test) {
    RepeatedTest::Message message{};
    message.strings.SetDecoder([this](RepeatedTest::StreamDecoder& decoder) {
      return this->CheckOne(decoder);
    });
    return repeated_test.Read(message);
  }

 private:
  Status CheckOne(RepeatedTest::StreamDecoder& decoder) {
    EXPECT_EQ(decoder.Field().value(), RepeatedTest::Fields::kStrings);

    std::array<char, 40> strings{};
    const StatusWithSize sws = decoder.ReadStrings(strings);
    EXPECT_EQ(sws.status(), OkStatus());
    EXPECT_EQ(sws.size(), kExpectedStrings[i_].size());
    EXPECT_EQ(std::memcmp(strings.data(),
                          kExpectedStrings[i_].data(),
                          kExpectedStrings[i_].size()),
              0);

    ++i_;
    return sws.status();
  }

  int i_ = 0;
  constexpr static std::string_view kExpectedStrings[] = {
      {"if music be the food of love, play on"},
      {"give me excess of it, that, surfeiting"},
      {"the appetite may sicken, and so die"}};
};

TEST(CodegenMessage, CallbackInClass) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // repeated.strings
    0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
    't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
    'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
    // repeated.strings
    0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
    's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
    ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
    // repeated.strings
    0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
    'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
    ' ', 's', 'o', ' ', 'd', 'i', 'e',
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  StringChecker checker{};
  const auto status = checker.Check(repeated_test);
  ASSERT_EQ(status, OkStatus());
}

// Check that we can create a custom subclass of the message struct that sets
// its own callbacks to member functions that populate fields added in the
// subclass.
struct CustomMessage : RepeatedTest::Message {
  CustomMessage() : RepeatedTest::Message() {
    strings.SetDecoder([this](RepeatedTest::StreamDecoder& decoder) {
      return this->ParseStrings(decoder);
    });
  }

  pw::Vector<std::array<char, 40>, 8> all_strings{};

 private:
  Status ParseStrings(RepeatedTest::StreamDecoder& decoder) {
    PW_ASSERT(decoder.Field().value() == RepeatedTest::Fields::kStrings);

    std::array<char, 40> one_strings{};
    const auto sws = decoder.ReadStrings(one_strings);
    if (!sws.ok()) {
      return sws.status();
    }

    one_strings[sws.size()] = '\0';
    all_strings.push_back(one_strings);

    return OkStatus();
  }
};

TEST(CodegenMessage, CallbackInSubclass) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // repeated.strings
    0x1a, 0x25, 'i', 'f', ' ', 'm', 'u', 's', 'i', 'c', ' ', 'b', 'e', ' ',
    't', 'h', 'e', ' ', 'f', 'o', 'o', 'd', ' ', 'o', 'f', ' ',
    'l', 'o', 'v', 'e', ',', ' ', 'p', 'l', 'a', 'y', ' ', 'o', 'n',
    // repeated.strings
    0x1a, 0x26, 'g', 'i', 'v', 'e', ' ', 'm', 'e', ' ', 'e', 'x', 'c', 'e',
    's', 's', ' ', 'o', 'f', ' ', 'i', 't', ',', ' ', 't', 'h', 'a', 't', ',',
    ' ', 's', 'u', 'r', 'f', 'e', 'i', 't', 'i', 'n', 'g',
    // repeated.strings
    0x1a, 0x23, 't', 'h', 'e', ' ', 'a', 'p', 'p', 'e', 't', 'i', 't', 'e', ' ',
    'm', 'a', 'y', ' ', 's', 'i', 'c', 'k', 'e', 'n', ',', ' ', 'a', 'n', 'd',
    ' ', 's', 'o', ' ', 'd', 'i', 'e',
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  RepeatedTest::StreamDecoder repeated_test(reader);

  CustomMessage message{};
  const auto status = repeated_test.Read(message);
  ASSERT_EQ(status, OkStatus());

  constexpr static std::string_view kExpectedStrings[] = {
      {"if music be the food of love, play on"},
      {"give me excess of it, that, surfeiting"},
      {"the appetite may sicken, and so die"}};

  EXPECT_EQ(message.all_strings.size(), 3u);
  for (unsigned short i = 0; i < 3; ++i) {
    EXPECT_EQ(std::memcmp(message.all_strings[i].data(),
                          kExpectedStrings[i].data(),
                          kExpectedStrings[i].size()),
              0);
    EXPECT_EQ(message.all_strings[i].data()[kExpectedStrings[i].size()], '\0');
  }
}

TEST(CodegenMessage, MaxSize) {
  // Verify constants generated from max_size options in full_test.pwpb_options
  static_assert(Pigweed::kErrorMessageMaxSize == 64);
  static_assert(Pigweed::kDataMaxSize == 8);

  Pigweed::Message size_message;
  EXPECT_EQ(size_message.error_message.max_size(),
            Pigweed::kErrorMessageMaxSize);
  EXPECT_EQ(size_message.data.max_size(), Pigweed::kDataMaxSize);

  // Verify constants generated from max_count options in repeated.pwpb_options
  static_assert(RepeatedTest::kUint32sMaxSize == 8);
  static_assert(RepeatedTest::kFixed32sMaxSize == 8);
  static_assert(RepeatedTest::kDoublesMaxSize == 2);
  static_assert(RepeatedTest::kUint64sMaxSize == 4);
  static_assert(RepeatedTest::kEnumsMaxSize == 4);

  RepeatedTest::Message count_message;
  EXPECT_EQ(count_message.uint32s.max_size(), RepeatedTest::kUint32sMaxSize);
  EXPECT_EQ(count_message.fixed32s.max_size(), RepeatedTest::kFixed32sMaxSize);
  EXPECT_EQ(count_message.doubles.max_size(), RepeatedTest::kDoublesMaxSize);
  EXPECT_EQ(count_message.uint64s.max_size(), RepeatedTest::kUint64sMaxSize);
  EXPECT_EQ(count_message.enums.max_size(), RepeatedTest::kEnumsMaxSize);
}

TEST(CodegenMessage, OneOf_Encode) {
  OneOfTest::Message message;

  int invocations = 0;
  message.type.SetEncoder([&invocations](OneOfTest::StreamEncoder& encoder) {
    invocations++;
    return encoder.WriteAnInt(32);
  });

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // type.an_int
    0x08, 0x20,
  };
  // clang-format on

  std::array<std::byte, 8> buffer;
  OneOfTest::MemoryEncoder oneof_test(buffer);

  EXPECT_EQ(oneof_test.Write(message), OkStatus());
  EXPECT_EQ(invocations, 1);

  EXPECT_EQ(oneof_test.size(), sizeof(expected_proto));
  EXPECT_EQ(
      std::memcmp(oneof_test.data(), expected_proto, sizeof(expected_proto)),
      0);
}

TEST(CodegenMessage, OneOf_Encode_MultipleTimes) {
  OneOfTest::Message message;

  int invocations = 0;
  message.type.SetEncoder([&invocations](OneOfTest::StreamEncoder& encoder) {
    invocations++;
    return encoder.WriteAString("oneof");
  });

  // clang-format off
  constexpr uint8_t expected_proto[] = {
    // type.a_string
    0x12, 0x05, 'o', 'n', 'e', 'o', 'f'
  };
  // clang-format on

  // Write the same message struct to two different buffers. Even though its
  // internal state is modified during the write, it should be logically const
  // with both writes successfully producing the same output.

  std::array<std::byte, 8> buffer_1;
  std::array<std::byte, 8> buffer_2;
  OneOfTest::MemoryEncoder oneof_test_1(buffer_1);
  OneOfTest::MemoryEncoder oneof_test_2(buffer_2);

  EXPECT_EQ(oneof_test_1.Write(message), OkStatus());
  EXPECT_EQ(invocations, 1);
  EXPECT_EQ(oneof_test_1.size(), sizeof(expected_proto));
  EXPECT_EQ(
      std::memcmp(oneof_test_1.data(), expected_proto, sizeof(expected_proto)),
      0);

  EXPECT_EQ(oneof_test_2.Write(message), OkStatus());
  EXPECT_EQ(invocations, 2);
  EXPECT_EQ(oneof_test_2.size(), sizeof(expected_proto));
  EXPECT_EQ(
      std::memcmp(oneof_test_2.data(), expected_proto, sizeof(expected_proto)),
      0);
}

TEST(CodegenMessage, OneOf_Encode_UnsetEncoderIsIgnored) {
  OneOfTest::Message message;
  std::array<std::byte, 8> buffer;
  OneOfTest::MemoryEncoder oneof_test(buffer);
  EXPECT_EQ(oneof_test.Write(message), OkStatus());
}

TEST(CodegenMessage, OneOf_Decode) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // type.a_message
    0x1a, 0x02, 0x08, 0x01,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  OneOfTest::StreamDecoder stream_decoder(reader);

  struct {
    OneOfTest::Fields field;
    OneOfTest::AMessage::Message submessage;
    int invocations = 0;
  } result;

  OneOfTest::Message message;
  message.type.SetDecoder(
      [&result](OneOfTest::Fields field, OneOfTest::StreamDecoder& decoder) {
        result.field = field;
        result.invocations++;
        if (field == OneOfTest::Fields::kAMessage) {
          return decoder.GetAMessageDecoder().Read(result.submessage);
        }
        return Status::InvalidArgument();
      });

  EXPECT_EQ(stream_decoder.Read(message), OkStatus());
  EXPECT_EQ(result.field, OneOfTest::Fields::kAMessage);
  EXPECT_EQ(result.invocations, 1);
  EXPECT_EQ(result.submessage.a_bool, true);
}

TEST(CodegenMessage, OneOf_Decode_MultipleOneOfFieldsFails) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // type.an_int
    0x08, 0x20,
    // type.a_message
    0x1a, 0x02, 0x08, 0x01,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  OneOfTest::StreamDecoder stream_decoder(reader);

  OneOfTest::Message message;
  message.type.SetDecoder(
      [](OneOfTest::Fields, OneOfTest::StreamDecoder&) { return OkStatus(); });

  EXPECT_EQ(stream_decoder.Read(message), Status::DataLoss());
}

TEST(CodegenMessage, OneOf_Decode_UnsetDecoderIsIgnored) {
  // clang-format off
  constexpr uint8_t proto_data[] = {
    // type.an_int
    0x08, 0x20,
  };
  // clang-format on

  stream::MemoryReader reader(as_bytes(span(proto_data)));
  OneOfTest::StreamDecoder stream_decoder(reader);
  OneOfTest::Message message;
  EXPECT_EQ(stream_decoder.Read(message), OkStatus());
}

}  // namespace
}  // namespace pw::protobuf
